/* Copyrights Security Engineering Research Group 
 * Author: Nauman (http://recluze.wordpress.com) 
 * 
 */

package edu.serg.mbar.ucon;

import java.io.FileOutputStream;
import java.util.Properties;
import java.util.Vector;

import javax.xml.parsers.*;
import javax.xml.transform.OutputKeys;


import edu.serg.mbar.ucon.interfaces.IsMutabilityCapable;
import edu.serg.mbar.ucon.interfaces.MutabilityEvent;
import edu.serg.mbar.ucon.interfaces.AttributeReader;
import edu.serg.mbar.ucon.interfaces.AttributeUpdateManager;
import edu.serg.mbar.ucon.meta.*;
import edu.serg.utils.*;

import org.apache.log4j.Logger;
import org.apache.xalan.templates.OutputProperties;
import org.apache.xml.serializer.Serializer;
import org.apache.xml.serializer.OutputPropertiesFactory;
import org.apache.xml.serializer.SerializerFactory;

import org.apache.xpath.*;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.w3c.dom.traversal.NodeIterator;



/**
 * 
 * 
 * @author Nauman (recluze.wordpress.com)
 * @author Sanaullah 
 * 
 */
public class AttributeManager implements IsMutabilityCapable, AttributeReader, AttributeUpdateManager {
	private static Logger log = Logger.getLogger(AttributeManager.class);
	private final static String OBJECTS_DIR=RegisteredStrings.OBJECTS_DIR;
	private XMLUtils xmlUtils= new XMLUtils();

	/**
	 * Process attribute Mutability event
	 * 
	 * @param event
	 *            MutabilityEvent to be handled
	 * 
	 * @return True if attribute successfully updated; false otherwise
	 */
	@Override
	public boolean processAttributeMutability(MutabilityEvent event) {
		log.info("Received request for attribute mutability of subject : "
				+ event.target);
		log.debug(" -- attribute: " + event.attribute);
		if (event.values != null) {
			log.debug(" -- values :  ");
			for (String x : event.values)
				log.debug(" ---- " + x);
		} else {
			log.debug(" -- no values provided");
		}

		// TODO: change the values now
		log.info("Changing attribute values for attribute.");
		return true;
	}

	@Override
	public Vector<String> requestAttributes(String target, String attribute) {
		// TODO Auto-generated method stub
		return null;
	}
	
	/**
	 * This Function will retrieve the value of an attribute 
	 * 
	 * @targetID
	 * 		The ID of the target, the attribute is associated with
	 * 
	 * @attribute
	 * 		The Name of the Attribute whose value is to be retrieved
	 * 
	 */
	
	public Vector<Object> getAttributeValue(String targetID, String attribute){
		Vector <Object> attributeValue = new Vector<Object>();
		try{
//			
			String xPath = "//obj:Object/obj:Attributes/obj:Attribute[obj:AttributeName='"+ attribute + "']/obj:AttributeValues/obj:AttributeValue";
		String targetFilePath = OBJECTS_DIR + targetID + ".xml";
		log.debug("Retrieve attribute("+ attribute  +")of Object(" + targetID + ")");
		
			NodeList attributeValueNodeList = xmlUtils.xPathEvaluateToNodeList(xmlUtils.parseXmlFromFile(targetFilePath), xPath);
			log.debug(attributeValueNodeList.getLength() +" Values for the attribute have been retrieved");
			for (int i=0 ; i<attributeValueNodeList.getLength();i++){
				log.debug("Value"+ (i+1) +" is = "+xmlUtils.getStringValueOfNode(attributeValueNodeList.item(i)) );
				attributeValue.add(xmlUtils.getStringValueOfNode(attributeValueNodeList.item(i)));
			}
		}catch (Exception e){
			log.error(e.getMessage());			
		}

		return attributeValue;
	}
	
	private Node getAttributeValuesNode(String targetID, String attributeName){
	
		String targetFilePath =OBJECTS_DIR + targetID +".xml";
		String xPath = "//obj:Object/obj:Attributes/obj:Attribute[obj:AttributeName='"+ attributeName + "']/obj:AttributeValues";
		Node attributeValuesNode;
		try
		{
		attributeValuesNode = xmlUtils.xPathEvaluateToNode(xmlUtils.parseXmlFromFile(targetFilePath), xPath);
		return attributeValuesNode;
		} catch (Exception e){
		e.printStackTrace();
		log.error(e.getMessage());
		return null;
		}
	
		
	}
	
	public void updateAttributeValue(String targetID, String attributeName, Vector<Object> updatedValue){
		log.info("Updating '"+ attributeName +"' Attribute of " + targetID +"");
		
		//get the attribute values node which may contain multiple AttributeValue nodes/(xml)Elements 
		Node attributeValuesNode = getAttributeValuesNode(targetID, attributeName);
		
		//output/display the original xml of the node
//		String xmlOfNode=""; 
//		xmlOfNode=xmlUtils.xmlFromNodetoString(attributeValuesNode);
//		log.debug("////////////////////////////////// The node Before the values were removed");
//		log.debug(xmlOfNode);
//		log.debug("//////////////////////////////////");
		
		/*
		 * to update the values we must first remove the values already present in the document
		 */
		log.debug("Removing old values of Attributes");
		
//		log.debug("--the number of child nodes within [" + attributeValuesNode.getNodeName() +"]  node are " + attributeValuesNode.getChildNodes().getLength());
		if (attributeValuesNode.hasChildNodes())
		{
			
			for (int i=attributeValuesNode.getChildNodes().getLength()-1;i>=0;i--)
			{
				attributeValuesNode.removeChild(attributeValuesNode.getChildNodes().item(i));
			}
//			log.debug("**the number of child nodes within [" + attributeValuesNode.getNodeName() +"]  node are " + attributeValuesNode.getChildNodes().getLength());
		}
		log.debug("Old Values of Attributes removed");
		//output the modified xml of the node
//		xmlOfNode =xmlUtils.xmlFromNodetoString(attributeValuesNode);
//		log.debug("////////////////////////////////// The node after the values were removed");
//		log.debug(xmlOfNode);
//		log.debug("//////////////////////////////////");
		
		// now append the new updated values to the node
		
		Document doc=attributeValuesNode.getOwnerDocument();
		String filePath =OBJECTS_DIR + targetID +".xml";//the path of the xml document to be saved
		MBARNamespaceContext ns =new MBARNamespaceContext();
		try{
//			doc =xmlUtils.parseXmlFromFile(filePath);
//			String xml =xmlUtils.xmlFromNodetoString(doc);
//			log.debug("//////////////////////////////////");
//			log.debug(xml);
//			log.debug("//////////////////////////////////");
			log.debug("Adding New values of attributes");
			for (int i=0;i<updatedValue.size();i++)
			{
				Text txt = doc.createTextNode(updatedValue.elementAt(i).toString());
				Element attributeValueElement =doc.createElementNS(ns.getNamespaceURI("obj"),"obj:AttributeValue");
				attributeValueElement.appendChild(txt);
				attributeValuesNode.appendChild(attributeValueElement);
//				attributeValuesNode.appendChild(doc.createElementNS(ns.getNamespaceURI("obj"),"obj:AttributeValue").appendChild(doc.createTextNode(updatedValue.elementAt(i).toString())));
			}
//			log.debug("***the number of child nodes after the new updated values were added within [" + attributeValuesNode.getNodeName() +"]  node are " + attributeValuesNode.getChildNodes().getLength());
			log.debug("New values have been added proceeding to save the document");
			
			saveAttributes(doc,filePath);
			log.info("Successfully Updated '" + attributeName + "' of target => " + targetID);
			
		}catch(Exception e){
			e.printStackTrace();
			log.error("error occured while updating attributes in document {"+ filePath + "}" + e.getMessage());
		}

		 		
	}
	
	private boolean saveAttributes(Document doc, String strFilePath)
	{
		log.info("Saving File");
		try{

            FileOutputStream OS = new FileOutputStream(strFilePath);

            Properties OP =  OutputPropertiesFactory.getDefaultMethodProperties("xml");

            OP.setProperty(OutputKeys.METHOD,"xml");

            OP.setProperty(OutputKeys.INDENT,"yes");

            Serializer serializer = SerializerFactory.getSerializer (OP);

            serializer.setOutputStream(OS);

            serializer.asDOMSerializer().serialize(doc);

            log.info("File Saved: " + strFilePath);

            return true;

		}catch(Exception e){

            e.printStackTrace();

            log.error("error while saving file :" + strFilePath + "  Not Saved!!!");

            return false;

		}
	}
	
	/**
	 * Resolves the target of the ObjectAttribute or TargetAttributeNode within a policy to determine whether the attribute belongs to another object or the object being requested or whether the attribute belongs to the Subject.
	 * @param objectAttribute
	 * 		The ObjectAttribute node or the TargetAttribute node of UCON policy document.
	 * @param subjectID
	 * 		The SubjectID of the subject which has requested to excercise a right on the document.		
	 * @param objectID
	 * 		The ObjectID which is being requested and whose associated policy is being evaluated.
	 * @return
	 * 		the ID of the object which is the target of the attribute.
	 */
	public String resolveTargetOfObjectAttributeNode(Node objectAttribute, String subjectID, String objectID)
	{
		String targetOfAttribute="";
		NodeList childNodes = objectAttribute.getChildNodes();
		int totalChildNodes = childNodes.getLength();
		for (int i=0; i<totalChildNodes ; i++)
		{
			Node child = childNodes.item(i);
			String nodeName = child.getNodeName();
//			String prefix = child.getPrefix();
		
			//determine if the nodeName contains a prefix
			if (nodeName.contains(":")) 
			{
				//Remove the prefix from the nodeName
				nodeName = nodeName.split(":", 2)[1];
			}
			
			if (nodeName.equalsIgnoreCase("ObjectID"))
			{
				//get the text node of the objectID
				Node txtNode = child.getChildNodes().item(0);
				//get the value(text) from the text node
				targetOfAttribute = txtNode.getNodeValue();
				break;
			}
			else
			{
				if (nodeName.equalsIgnoreCase("RequestedObject"))
				{
					targetOfAttribute= objectID;
					break;
				}
				else
				{
					if (nodeName.equalsIgnoreCase("RequestingSubject"))
					{
						targetOfAttribute = subjectID;
						break;
					}
				}
			}
		}
		return targetOfAttribute;
	}
}
